﻿/*
* Copyright (c) 2010-2012 Tesla Engine Group
* 
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* 
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* 
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/


using System;
using Tesla.Core;
using Tesla.Math;

namespace Tesla.Graphics {
    /// <summary>
    /// Abstract renderer that serves as the base class for implementation specific renderers.
    /// </summary>
    public abstract class BaseRenderer : IRenderer {
        private ICamera _currentCamera;
        private bool _isDisposed;
        private IRenderQueue _renderQueue;
        private RenderStateList _renderStateCache;
        private RenderStateList _enforcedStates;
        private Material _enforcedMaterial;

        /// <summary>
        /// Event for when the renderer is in the process of disposing itself, where it will clean up all resources.
        /// </summary>
        public event EventHandler Disposing;

        /// <summary>
        /// Gets if the renderer has been disposed.
        /// </summary>
        public bool IsDisposed {
            get {
                return _isDisposed;
            }
        }

        /// <summary>
        /// Gets or sets the current camera. A camera owns a viewport and is used to perform culling.
        /// </summary>
        public ICamera CurrentCamera {
            get {
                return _currentCamera;
            }
            set {
                _currentCamera = value;
                Engine.ValueMap.Camera = value;
                if(_currentCamera != null) {
                    this.Viewport = _currentCamera.Viewport;
                }
            }
        }

        /// <summary>
        /// Gets or sets the render queue that will be used to place renderables, sort them, and render them to the render target.
        /// </summary>
        public IRenderQueue RenderQueue {
            get {
                return _renderQueue;
            }
            set {
                _renderQueue = value;
            }
        }

        /// <summary>
        /// Gets or sets the viewport which identifies the portion of the render target which we will be rendering onto.
        /// </summary>
        public abstract Viewport Viewport {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the rectangle used for scissor testing.
        /// </summary>
        public abstract Rectangle ScissorRectangle {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the blend factor which is a constant color used for alpha blending. Default is Color.White. This is a "high frequency" render state,
        /// and setting a BlendState to the renderer will override this value.
        /// </summary>
        public abstract Color BlendFactor {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the bitmask which defines which samples can be written during multisampling. Default is -1 (0xffffffff). This is a "high frequency" render
        /// state, and setting a BlendState to the renderer will override this value.
        /// </summary>
        public abstract int MultiSampleMask {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the reference value for stencil testing. Default is zero. This is a "high frequency" render state, and setting a BlendState to the
        /// renderer will override this value.
        /// </summary>
        public abstract int ReferenceStencil {
            get;
            set;
        }

        /// <summary>
        /// Gets the sampler state collection for setting/getting sampler states used by the pixel shader.
        /// </summary>
        public abstract ISamplerStateCollection SamplerStates {
            get;
        }

        /// <summary>
        /// Gets the sampler state collection for setting/getting sampler states used by the vertex shader.
        /// </summary>
        public abstract ISamplerStateCollection VertexSamplerStates {
            get;
        }

        /// <summary>
        /// Gets the texture collection for setting/getting textures used by the pixel shader.
        /// </summary>
        public abstract ITextureCollection Textures {
            get;
        }

        /// <summary>
        /// Gets the texture collection for setting/getting textures used by the vertex shader.
        /// </summary>
        public abstract ITextureCollection VertexTextures {
            get;
        }

        /// <summary>
        /// Gets or sets the BlendState currently active on the device and in the cache. Default is BlendState.Opaque. This is equivalent to using the
        /// Get/ApplyRenderState methods.
        /// </summary>
        public BlendState BlendState {
            get {
                BlendState bs = _renderStateCache[RenderStateType.BlendState] as BlendState;
                if(bs == null) {
                    return RenderStateList.DefaultBlendState;
                }
                return bs;
            }
            set {
                ApplyRenderState(RenderStateType.BlendState, value);
            }
        }

        /// <summary>
        /// Gets or sets the DepthStencilState currently active on the device and in the cache. Default is DepthStencilState.Default. This is equivalent to
        /// using the Get/ApplyRenderState methods.
        /// </summary>
        public DepthStencilState DepthStencilState {
            get {
                DepthStencilState dss = _renderStateCache[RenderStateType.DepthStencilState] as DepthStencilState;
                if(dss == null) {
                    return RenderStateList.DefaultDepthStencilState;
                }
                return dss;
            }
            set {
                ApplyRenderState(RenderStateType.DepthStencilState, value);
            }
        }

        /// <summary>
        /// Gets or sets the RasterizerState currently active on the device and in the cache. Default is RasterizerState.CullBackClockwiseFront. This
        /// is the equivalent to using the Get/ApplyRenderState methods.
        /// </summary>
        public RasterizerState RasterizerState {
            get {
                RasterizerState rs = _renderStateCache[RenderStateType.RasterizerState] as RasterizerState;
                if(rs == null) {
                    return RenderStateList.DefaultRasterizerState;
                }
                return rs;
            }
            set {
                ApplyRenderState(RenderStateType.RasterizerState, value);
            }
        }

        /// <summary>
        /// Gets or sets the enforced BlendState, this will override the cached BlendState when ApplyRenderState() is called.
        /// This is equivalent to using the Get/SetEnforcedState methods.
        /// </summary>
        public BlendState EnforcedBlendState {
            get {
                return _enforcedStates[RenderStateType.BlendState] as BlendState;
            }
            set {
                _enforcedStates[RenderStateType.BlendState] = value;
            }
        }

        /// <summary>
        /// Gets or sets the enforced DepthStencilState, this will override the cached DepthStencilState when ApplyRenderState() is called.
        /// This is equivalent to using the Get/SetEnforcedState methods.
        /// </summary>
        public DepthStencilState EnforcedDepthStencilState {
            get {
                return _enforcedStates[RenderStateType.DepthStencilState] as DepthStencilState;
            }
            set {
                _enforcedStates[RenderStateType.DepthStencilState] = value;
            }
        }

        /// <summary>
        /// Gets or sets the enforced RasterizerState, this will override the cached RasterizerState when ApplyRenderState() is called.
        /// This is equivalent to using the Get/SetEnforcedState methods.
        /// </summary>
        public RasterizerState EnforcedRasterizerState {
            get {
                return _enforcedStates[RenderStateType.RasterizerState] as RasterizerState;
            }
            set {
                _enforcedStates[RenderStateType.RasterizerState] = value;
            }
        }

        /// <summary>
        /// Gets or sets the enforced material. This may or may not be ignored when the IRenderable's Render() method is called. If this is not
        /// null, it lets the renderable know what material to use instead of its default material. Set this value to null for default behavior.
        /// </summary>
        public Material EnforcedMaterial {
            get {
                return _enforcedMaterial;
            }
            set {
                _enforcedMaterial = value;
            }
        }

        /// <summary>
        /// Gets the graphics adapter that was used to create the renderer.
        /// </summary>
        public abstract IGraphicsAdapter Adapter {
            get;
        }

        /// <summary>
        /// Creates a new instance of <see cref="BaseRenderer"/>.
        /// </summary>
        public BaseRenderer() {
            _renderQueue = new RenderQueue(this);
            _renderStateCache = new RenderStateList();
            _enforcedStates = new RenderStateList();
        }

        /// <summary>
        /// Releases unmanaged resources and performs other cleanup operations before the
        /// <see cref="BaseRenderer"/> is reclaimed by garbage collection.
        /// </summary>
        ~BaseRenderer() {
            Dispose(false);
        }

        /// <summary>
        /// Clears all bounded render targets.
        /// </summary>
        /// <param name="color">Color to clear to</param>
        public void Clear(Color color) {
            Clear(ClearOptions.Target | ClearOptions.Depth | ClearOptions.Stencil, color, 1.0f, 0);
        }

        /// <summary>
        /// Clears all bounded render targets.
        /// </summary>
        /// <param name="options">Options specifying which buffer to clear - Color, Depth, and Stencil</param>
        /// <param name="color">Color to clear to</param>
        /// <param name="depth">Depth value to clear to</param>
        /// <param name="stencil">Stencil value to clear to</param>
        public abstract void Clear(ClearOptions options, Color color, float depth, int stencil);

        /// <summary>
        /// Sets a 2D render target to the first render target slot and unbinds
        /// all currently bound targets.
        /// Setting this to null
        /// unbinds all targets and sets the target to the currently active
        /// swap chain's back buffer.
        /// </summary>
        /// <param name="renderTarget">Render target to bind</param>
        public abstract void SetRenderTarget(RenderTarget2D renderTarget);

        /// <summary>
        /// Sets a Cube render target, to the first render target slot
        /// and unbinds all currently bound targets.
        /// Setting this to null unbinds all targets and sets the target to the currently active
        /// swap chain's back buffer.
        /// </summary>
        /// <param name="renderTarget">Render target to bind</param>
        /// <param name="cubeMapFace">Which face of the cube map to bind</param>
        public abstract void SetRenderTarget(RenderTargetCube renderTarget, CubeMapFace cubeMapFace);

        /// <summary>
        /// Sets the render targets to the device and unbinds all currently
        /// bound targets. The first target in the array is bound to the first
        /// slot and so forth. The first render target's depth-stencil buffer is used
        /// for all targets. The first render target's depth-stencil buffer is used
        /// for all targets.
        /// </summary>
        /// <param name="renderTargets">Array of targets</param>
        public abstract void SetRenderTargets(params RenderTargetBinding[] renderTargets);

        /// <summary>
        /// Gets the currently bounded render targets.
        /// </summary>
        /// <returns>
        /// Current render targets
        /// </returns>
        public abstract RenderTargetBinding[] GetRenderTargets();

        /// <summary>
        /// Sets the vertex buffer.
        /// </summary>
        /// <param name="vb">The vb.</param>
        public abstract void SetVertexBuffer(VertexBuffer vb);

        /// <summary>
        /// Sets the vertex buffer to the first input slot onto the device
        /// </summary>
        /// <param name="vertexBuffer">Vertex buffer to bind</param>
        /// <param name="vertexOffset">Offset from the beginning of the buffer to the first vertex to use (in bytes).</param>
        public abstract void SetVertexBuffer(VertexBuffer vertexBuffer, int vertexOffset);

        /// <summary>
        /// Sets an array of vertex buffers starting from the first input slot.
        /// </summary>
        /// <param name="vertexBuffers">Array of VertexBufferBindings, each binding should specify a vertex buffer, a vertex offset, and an instancing frequency.</param>
        public abstract void SetVertexBuffers(params VertexBufferBinding[] vertexBuffers);

        /// <summary>
        /// Gets the currently bounded vertex buffers.
        /// </summary>
        /// <returns>
        /// Current render targets
        /// </returns>
        public abstract VertexBufferBinding[] GetVertexBuffers();

        /// <summary>
        /// Sets the index buffer.
        /// </summary>
        /// <param name="ib">The ib.</param>
        public abstract void SetIndexBuffer(IndexBuffer ib);

        /// <summary>
        /// Gets the currently bound index buffer.
        /// </summary>
        /// <returns>
        /// Current index buffer
        /// </returns>
        public abstract IndexBuffer GetIndexBuffer();

        /// <summary>
        /// Draws non-indexed geometry.
        /// </summary>
        /// <param name="primType">Type of the primitives to draw</param>
        /// <param name="vertexCount">Number of vertices to draw</param>
        /// <param name="startVertex">Starting index in the vertex buffer at which to read vertices from.</param>
        public abstract void Draw(PrimitiveType primType, int vertexCount, int startVertex);

        /// <summary>
        /// Draws indexed geometry.
        /// </summary>
        /// <param name="primType">Type of the primitives to draw.</param>
        /// <param name="indexCount">Number of indices to draw.</param>
        /// <param name="startIndex">Starting index in the index buffer at which to read vertex indices from.</param>
        /// <param name="baseVertex">Offset to add to each vertex index in the index buffer.</param>
        public abstract void DrawIndexed(PrimitiveType primType, int indexCount, int startIndex, int baseVertex);

        /// <summary>
        /// Draws indexed, instanced geometry.
        /// </summary>
        /// <param name="primType">Type of the primitives to draw.</param>
        /// <param name="indexCountPerInstance">Number of indices per instance to draw.</param>
        /// <param name="startIndex">Starting index in the index buffer at which to read vertex indices from.</param>
        /// <param name="baseVertex">Offset to add to each vertex index in the index buffer.</param>
        /// <param name="instanceCount">Number of instances to draw.</param>
        public abstract void DrawIndexedInstanced(PrimitiveType primType, int indexCountPerInstance, int startIndex, int baseVertex, int instanceCount);

        /// <summary>
        /// Applies the render state to the device. If the state is null, then the default state for its type is used instead. If the renderer is
        /// enforcing a state for the specified state type, that is used instead. If the state to be applied already is present in the cache, the renderer returns,
        /// preventing redundant state switching.
        /// It is important to let the renderer take care of state management, and not to set states directly in your shader program, in your effect pass. Otherwise,
        /// the render states may be inconsistent to what is present in the cache.
        /// </summary>
        /// <param name="stateType">Enumerated render state type to apply</param>
        /// <param name="state">Render state to apply to the device, if null the default state is used based on the enumerated type.</param>
        public void ApplyRenderState(RenderStateType stateType, RenderState state) {
            //Silently fail if someone is trying to give us a sampler state.
            if(stateType == RenderStateType.SamplerState || state is SamplerState) {
                return;
            }

            //Get the enforced state
            RenderState tempState = GetEnforcedState(stateType);

            //Is enforced state null? If so, use supplied state
            if(tempState == null) {
                tempState = state;
            }

            //If we're STILL null, we then have to use the default state, so query it
            if(tempState == null) {
                tempState = GetDefaultState(stateType);
            }

            //Get the cached state
            RenderState cachedState = GetRenderState(stateType);

            //Do we have a cached state? Usually we should.
            if(cachedState != null) {
                //Have we already applied the same state? If so, do nothing
                if(cachedState.RenderStateKey == tempState.RenderStateKey) {
                    return;
                } else {
                    //Otherwise set to the device and cache it
                    ApplyConcreteState(tempState);
                    _renderStateCache[stateType] = tempState;
                }
            } else {
                //Since the cached state is null, we don't know what's on the device, so we apply the state and set it in the cache
                //This should only occur at startup when we clear the states for the first time
                ApplyConcreteState(tempState);
                _renderStateCache[stateType] = tempState;
            }
        }

        //Helper method for getting the default state based on enumeration type.
        private RenderState GetDefaultState(RenderStateType stateType) {
            switch(stateType) {
                case RenderStateType.BlendState:
                    return RenderStateList.DefaultBlendState;
                case RenderStateType.DepthStencilState:
                    return RenderStateList.DefaultDepthStencilState;
                case RenderStateType.RasterizerState:
                    return RenderStateList.DefaultRasterizerState;
                default:
                    return null; //Shouldn't occur
            }
        }

        /// <summary>
        /// Method for applying the actual platform-specific render state onto the device.
        /// </summary>
        /// <param name="state">State to be applied</param>
        protected abstract void ApplyConcreteState(RenderState state);

        /// <summary>
        /// Returns the current renderstate of the specified type that has been set to the device, and is present in the cache.
        /// </summary>
        /// <param name="stateType">State type to query</param>
        /// <returns>
        /// The render state
        /// </returns>
        public RenderState GetRenderState(RenderStateType stateType) {
            if(stateType == RenderStateType.SamplerState) {
                return null;
            }
            return _renderStateCache[stateType];
        }

        /// <summary>
        /// Clears the state of the render.
        /// </summary>
        /// <param name="type">The type.</param>
        public void ClearRenderState(RenderStateType type) {
            ApplyRenderState(type, null);
        }

        /// <summary>
        /// Clears the renderer's render states, setting all state objects to their default values.
        /// </summary>
        public void ClearRenderStates() {
            _renderStateCache.Clear();
            ApplyRenderState(RenderStateType.BlendState, RenderStateList.DefaultBlendState);
            ApplyRenderState(RenderStateType.DepthStencilState, RenderStateList.DefaultDepthStencilState);
            ApplyRenderState(RenderStateType.RasterizerState, RenderStateList.DefaultRasterizerState);
        }

        /// <summary>
        /// Sets the enforced state that will override the incoming state in ApplyRenderState() method.
        /// </summary>
        /// <param name="state">State to enforce</param>
        public void SetEnforcedState(RenderState state) {
            if(state == null || state.StateType == RenderStateType.SamplerState) {
                return;
            }
            _enforcedStates[state.StateType] = state;
        }

        /// <summary>
        /// Returns the enforced state specified by its type, if it exists.
        /// </summary>
        /// <param name="stateType">State's type</param>
        /// <returns>
        /// Enforced state, or null if this state type is not enforced
        /// </returns>
        public RenderState GetEnforcedState(RenderStateType stateType) {
            if(stateType == RenderStateType.SamplerState) {
                return null;
            }
            return _enforcedStates[stateType];
        }

        /// <summary>
        /// Clears the specific render state from the renderer.
        /// </summary>
        /// <param name="stateType">State type to clear</param>
        public void ClearEnforcedState(RenderStateType stateType) {
            if(stateType == RenderStateType.SamplerState) {
                return;
            }
            _enforcedStates[stateType] = null;
        }

        /// <summary>
        /// Clears all enforced states from the renderer.
        /// </summary>
        public void ClearEnforcedStates() {
            _enforcedStates.Clear();
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose() {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Releases unmanaged and - optionally - managed resources
        /// </summary>
        /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
        protected virtual void Dispose(bool disposing) {
            _isDisposed = true;
            OnDispose();
        }

        /// <summary>
        /// Called when the renderer is disposing.
        /// </summary>
        protected virtual void OnDispose() {
            if(Disposing != null) {
                Disposing(this, EventArgs.Empty);
            }
        }
    }
}
